package horde.internalResources;

import horde.control.AutomatonBehavior;
import horde.interfaces.ItfBehavior;
import horde.interfaces.Requests;
import horde.patterns.Agent;
import horde.patterns.Core;
import horde.patterns.Msg;
import horde.internalResources.dictionaries.dictionary;
import horde.utils.SynchronizedQueue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;

/**
 * Store for all the information and configuration of the components of the platform
 * @author Arturo Mazón
 * @author Andres Picazo
 */
public class Directory {

    // <editor-fold defaultstate="collapsed" desc="Atributes">
    private static final String nameDir = dictionary.getString("dir.nameDir");
    private static Directory instance = null;
    private HashMap table, sharedResources, groups;
    private Properties masterProperties;
    private Requests requests;
    private Properties routes;

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Singleton">
    private synchronized static void createInstance() {
        if (instance == null) {
            instance = new Directory();
        }
    }

    private Directory() {
        table = new HashMap();
        sharedResources = new HashMap();
        requests = null;
        routes = null;
        groups = new HashMap();
        groups.put("default", null);
    }

    public static Directory getInstance() {
        if (instance == null) {
            createInstance();
        }
        return instance;
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Methods">
    /**
     * Getter of the size of the directory's hash map
     * @return Integer that represents the size of the Directory
     */
    public int getSize() {
        return table.size();
    }

    /**
     * Getter of all the keys of the Directory's hash map.  Every key is an agent name.
     * @return A set of keys of the Directory
     */
    public Set getKeys() {
        return table.keySet();
    }

    /**
     * Gettter of agents' IDs by their position in the directory's map
     * @param i Represents the position of an agent in the directory's map
     * @return The ID of the agent required
     */
    public String getAgentID(int i) {
        Object[] aux = table.keySet().toArray();
        return (String) aux[i];
    }

    /**
     * Adds a specified message to the box of the destination agent
     * @param msg Message sent
     * @return True if message added to directory, false if not
     */
    public boolean putMessage(Msg msg) {
        if (table.containsKey(msg.getDestination())) {
            try {
                getAgentBox(msg.getDestination()).offer(msg);
            } catch (Exception ex) {
                return false;
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Adds a specified agent with his properties to the directory map
     * @param agent Complete agent to register
     * @param properties Set of properties of the specified agent
     * @return True if the agent is added, false  if not.
     */
    public boolean addAgent(Agent agent, Properties properties) {
        if (table.containsKey(agent.getID())) {
            return false;
        } else {
            /* If the agent has an automaton, get it */
            ItfBehavior b = agent.getBehavior();
            AutomatonBehavior automaton = (b instanceof AutomatonBehavior) ? (AutomatonBehavior) b : null;
            /* Creates a new storage entry for the agent */
            Storage storage = new Storage(agent.getCore(), new Thread(agent), new SynchronizedQueue(), automaton, properties);
            /* Adds the new entry to the directory */
            table.put(agent.getID(), storage);
            /* returns ok */
            return true;
        }
    }

    /**
     * Removes all the information of an agent specified by his ID
     * @param agentID ID of the agent to remove
     * @return True if the agent is removed, false if not
     */
    public boolean removeAgent(String agentID) {
        if (!table.containsKey(agentID)) {
            return false;
        } else {
            table.remove(agentID);
        }
        return true;
    }

    @Deprecated
    public boolean addEntry(String agentID, Thread agentThread, SynchronizedQueue box, ArrayList resources) {
        if (table.containsKey(agentID)) {
            return false;
        } else {
            Storage storage = new Storage(null, agentThread, box, resources, null);
            table.put(agentID, storage);
            return true;
        }
    }



    /**
     * Replaces the current box of an existing agent in the directory by a new one
     * @param agentID Id of the agent that receives the new box of messages
     * @param box New box to be added to the specified agent
     * @return True if the new box is added, false if not
     */
    public boolean setBox(String agentID, SynchronizedQueue box) {
        if (!table.containsKey(agentID)) {
            return false;
        } else {
            Storage s = (Storage) table.get(agentID);
            s.agentBox = box;
            return true;
        }
    }

    /**
     * Replaces the current thread of an existing agent in the directory by a new one
     * @param agentID Id of the agent that receives the new thread
     * @param box New thread to be added to the specified agent
     * @return True if the new thread is added, false if not
     */
    public boolean setThread(String agentID, Thread agentThread) {
        if (!table.containsKey(agentID)) {
            return false;
        } else {
            Storage s = (Storage) table.get(agentID);
            s.agentThread = agentThread;
            return true;
        }
    }

    @Deprecated
    public boolean setResources(String agentID, ArrayList resources) {
        if (!table.containsKey(agentID)) {
            return false;
        } else {
            Storage s = (Storage) table.get(agentID);
            s.agentResources = resources;
            return true;
        }
    }

    /**
     * Check if an agent ID is registered in the directory
     * @param agentID Agent ID to be checked
     * @return True if the agent is registered, false if not
     */
    public boolean containsAgentID(String agentID) {
        return table.containsKey(agentID);
    }

    /**
     * Gets the complete core of a specified agent
     * @param agentID ID of the agent owner of the core required
     * @return Core of the agent specified if exists, null if not
     */
    public Core getAgentCore(String agentID) {
        if (!table.containsKey(agentID)) {
            return null;
        } else {
            return ((Storage) table.get(agentID)).agentCore;
        }
    }

    /**
     * Gets the complete thread of a specified agent
     * @param agentID ID of the agent owner of the thread required
     * @return Thread of the agent specified if exists, null if not
     */
    public Thread getAgentThread(String agentID) {
        if (!table.containsKey(agentID)) {
            return null;
        } else {
            return ((Storage) table.get(agentID)).agentThread;
        }
    }

    /**
     * Gets the complete behavior of an specified agent
     * @param agentID ID of the agent owner of the behavior required
     * @return Behavior of the agent specified if exists, null if not
     */
    public AutomatonBehavior getAutomaton(String agentID) {
        if (!table.containsKey(agentID)) {
            return null;
        } else {
            return ((Storage) table.get(agentID)).agentAutomaton;
        }
    }

    /**
     * Gets the complete box of a specified agent
     * @param agentID ID of the agent owner of the box required
     * @return Box of the agent specified if exists, null if not
     */
    public SynchronizedQueue getAgentBox(String agentID) {
        if (!table.containsKey(agentID)) {
            return null;
        } else {
            return ((Storage) table.get(agentID)).agentBox;
        }
    }

    @Deprecated
    public ArrayList getAgentResources(String agentID) {
        if (!table.containsKey(agentID)) {
            return null;
        } else {
            return ((Storage) table.get(agentID)).agentResources;
        }
    }

    /**
     * Gets a specific property represented by its name of a specified agent
     * @param agentID ID of the agent owner of the required property
     * @param propertyName Name of the desired property
     * @return The value of the required property if exists, null if not
     */
    public String getAgentProperty(String agentID, String propertyName) {
        if (!table.containsKey(agentID)) {
            return null;
        } else {
            Storage s = (Storage) table.get(agentID);
            if (s.agentProperties != null) {
                return s.agentProperties.getProperty(propertyName);
            } else {
                return null;
            }
        }
    }

    /**
     * Gets the complete set of properties of a specified agent
     * @param agentID ID of the agent owner of the required properties
     * @return The set of required properties if exists, null if not
     */
    public Properties getAgentProperties(String agentID) {
        if (!table.containsKey(agentID)) {
            return null;
        } else {
            return ((Storage) table.get(agentID)).agentProperties;
        }
    }

    /**
     * Sets the aplication properties for the master agent
     * @param p Properties to be set
     */
    public void setMasterProperties(Properties p) {
        masterProperties = p;
    }

    /**
     * Gets the current aplication properties for the master agent
     * @return The current aplication properties for the master agent
     */
    public Properties getMasterProperties() {
        return masterProperties;
    }

    /**
     * Gets a specific property of the  current aplication properties for the master agent
     * @return The name of the required roperty of the current aplication properties for the master agent
     */
    public String getMasterProperty(String propertyName) {
        if (masterProperties != null) {
            return masterProperties.getProperty(propertyName);
        } else {
            return null;
        }
    }

    /**
     * Sets a new routes table in the directory to be interpreted by the master agent
     * @param routes New set of routes
     */
    public void setRoutes(Properties routes) {
        this.routes = routes;
        System.err.println("Routes read: " + routes.size());
        for (Object p : routes.keySet()) {
            System.err.println(p.toString() + " : " + routes.getProperty(p.toString()));
        }
    }

    /**
     * Gets the routes table of the directory
     * @return the routes table of the directory
     */
    public Properties getRoutes() {
        return routes;
    }

    /**
     * Gets the route for a specific agent
     * @param destination Name of the agent owner of the required route
     * @return The route for destination agent
     */
    public String getRouteFor(String destination) {
        return routes.getProperty(destination).toString();
    }

    /**
     * Adds a new route to a specific agent in a specific node
     * @param destination Name of the destination agent
     * @param location Node
     */
    public void addRoute(String destination, String location) {
        if (routes == null) {
            routes = new Properties();
        }
        routes.put(destination, location);
    }

    /**
     * The same as getSize()
     * @return Size of the hash map of the directory
     */
    public int numberOfElements() {
        return table.size();
    }

    /**
     * Gets the complete description of a specified agent
     * @param agentID ID of the agent owner of the required description
     * @return Array of description if exists, null if not
     */
    public ArrayList agentDescription(String agentID) {
        if (!table.containsKey(agentID)) {
            return null;
        } else {
            ArrayList agentData = new ArrayList();
            Storage data = (Storage) table.get(agentID);
            agentData.add(data.agentThread == null);
            agentData.add(data.agentBox == null);
            agentData.add(data.getNumberResources());
            return agentData;
        }
    }

    /**
     * Gets all the agent names registered in the directory
     * @return Array of agent names
     */
    public ArrayList getAgentNames() {
        Object[] aux = table.keySet().toArray();
        ArrayList names = new ArrayList(aux.length);
        for (Object o_name : aux) {
            names.add((String) o_name);
        }
        return names;
    }

    public void addGroupMember (String memberID, String groupID) {
        if (groupID == null) groupID = "default";
        if (groups.containsKey(groupID.toLowerCase())) {
            String members = (String)groups.get(groupID);
            if (members == null) members = memberID;
            else members = members + " " + memberID;
            groups.put(groupID.toLowerCase(), members);
        } else {
            groups.put(groupID.toLowerCase(), memberID);
        }        
    }

    /**
     * Gets all the agent names belonging to a specific group
     * @param group
     * @return Array of agent names belonging to the group
     */
    public ArrayList<String> getGroupMembers(String group) {
        ArrayList<String>names = new ArrayList<String>();
        /* Get the group */
        String list = (String)groups.get(group);
        StringTokenizer st = new StringTokenizer(list);
        while (st.hasMoreTokens()) names.add(st.nextToken());
        return (names.isEmpty()) ? null : names;


//        Object[] aux = table.keySet().toArray();
//        ArrayList<String> names = new ArrayList(aux.length);
//        for (Object o_name : aux) {
//            String name = (String) o_name;
//            String agent_group = getAgentProperty(name, "group");
//            if (group.equals(agent_group)) {
//                names.add((String) o_name);
//            }
//        }
//        return (names.isEmpty()) ? null : names;
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Shared Resources">
    /**
     * Adds a complete shared resource to the directory
     * @param id Shared resource id
     * @param type Shared resource type
     * @param resource Object that represent the new shared resource
     */
    public void addSharedResource(String id, String type, Object resource) {
        Shared sharedResource = new Shared(type, resource);
        sharedResources.put(id, sharedResource);
    }

    /**
     * Gets the type of a specific shared resource searched by a given id
     * @param id ID of the shared resource required
     * @return The type of the shared resource desired
     */
    public String getResourceType(String id) {
        Shared s = (Shared) sharedResources.get(id);
        if (s != null) {
            return s.classSource;
        } else {
            return null;
        }
    }

    /**
     * Gets the object that represents a specific shared resource searched by a given id
     * @param id ID of the shared resource required
     * @return The complete object of the shared resource desired
     */
    public Object getSharedResource(String id) {
        Shared s = (Shared) sharedResources.get(id);
        if (s != null) {
            return s.object;
        } else {
            return null;
        }
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Requests">
    /**
     * Gets the object of requests in the directory
     * @see Requests
     * @return The complete object of requests
     */
    public Requests getRequests() {
        return requests;
    }

    /**
     * Sets the object of requests in the directory
     * @param r New requests to be set
     * @see Requests 
     */
    public void setRequests(Requests r) {
        requests = r;
    }



    // </editor-fold>

    /* Auxiliary clases */
    // <editor-fold defaultstate="collapsed" desc="Storage, used for agents">
    /**
     * This class represents all the information needed by every agent: core,thread, box, resources and properties.
     */
    private class Storage {

        // <editor-fold defaultstate="collapsed" desc="Builders">
        public Storage() {
            agentThread = null;
            agentBox = null;
            agentResources = null;
            agentProperties = null;
            agentCore = null;
        }

        public Storage(Core core, Thread thread, SynchronizedQueue box, ArrayList resources, Properties properties) {
            agentThread = thread;
            agentBox = box;
            agentResources = resources;
            agentProperties = properties;
            agentCore = core;
        }

        public Storage(Core core, Thread thread, SynchronizedQueue box, AutomatonBehavior automaton, Properties properties) {
            agentThread = thread;
            agentBox = box;
            agentResources = null;
            agentProperties = properties;
            agentCore = core;
            agentAutomaton = automaton;
        }
        // </editor-fold>

        @Deprecated
        public int getNumberResources() {
            return agentResources.size();
        }

        /**
         * Store thread, box, resources and properties of an agent in an ArrayList.
         * @return {agentThread, agentBox, agentResources, agentProperties}
         */
        public ArrayList toArrayList() {
            ArrayList arr = new ArrayList(4);
            arr.add(0, agentThread);
            arr.add(1, agentBox);
            arr.add(2, agentResources);
            arr.add(3, agentProperties);
            return arr;
        }
        /// <editor-fold defaultstate="collapsed" desc="Atributes">
        protected Thread agentThread;
        protected SynchronizedQueue agentBox;
        protected ArrayList agentResources;
        protected Properties agentProperties;
        protected Core agentCore;
        protected AutomatonBehavior agentAutomaton;
        // </editor-fold>
    }

    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Shared">
    /**
     * Class that stores all the information of a shared resource
     */
    public class Shared {

        /// <editor-fold defaultstate="collapsed" desc="Atributes">
        protected String classSource;
        protected Object object;

        // </editor-fold>
        /// <editor-fold defaultstate="collapsed" desc="Builder">
        public Shared(String classSource, Object object) {
            this.classSource = classSource;
            this.object = object;
        }

        // </editor-fold>
        // <editor-fold defaultstate="collapsed" desc="Getters">
        public String getClassType() {
            return classSource;
        }

        public Object getResource() {
            return object;
        }
        // </editor-fold>
    }
    // </editor-fold>
    
}


